//包含头文件
#include<sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <sys/shm.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>

#define PORT 3333

struct message
{
    long type;
    char buf[1024];
}mybuf;

// 共享内存结构体，用于存储群聊消息
struct SharedMemoryData {
    int messageCount;  // 记录当前共享内存中消息的数量
    struct Message {
        char name[20];  // 存储发送消息的用户姓名
        char time[30];  // 存储消息发送时间
        char content[1024];  // 存储消息内容
    } messages[10];  // 假设最多存储10条群聊消息，可根据实际调整大小
};

char name0[20];

int ex=20;

// 全局变量，用于存储共享内存标识符
int shmid;
// 全局变量，指向共享内存地址的指针
struct SharedMemoryData *shmData;
// 用于标记共享内存写入线程是否结束的变量
int writeThreadEnd = 0;
// 用于标记共享内存读取线程是否结束的变量
int readThreadEnd = 0;

// 用于标记共享内存读取线程是否阻塞的变量，初始化为1表示阻塞
int readThreadBlocked = 1;

// 用于存储上次读取的消息内容，方便比较是否有新消息
char lastReadMessage[1024] = "";

void *readSharedMemory(void *arg)
{
    // printf("********共享内存读取线程已启动****************\n");
    while (!readThreadEnd)
    {
        // 根据标记决定是否阻塞
        while (readThreadBlocked)
        {
            usleep(10000);  // 阻塞时可以适当休眠，避免过度占用CPU资源
            continue;
        }

        // 检查共享内存中是否有消息
        if (shmData->messageCount > 0)
        {
            // 比较当前messages[0]与上次接收的消息是否不同
            if (strcmp(shmData->messages[0].content, lastReadMessage)!= 0)
            {
                printf("%s %s 在群聊说：%s\n", shmData->messages[0].time, shmData->messages[0].name, shmData->messages[0].content);
                // 更新上次接收的消息为当前messages[0]
                strcpy(lastReadMessage, shmData->messages[0].content);
            }
        }
        // 这里可以添加一个机制，比如等待一段时间（使用例如sleep或者usleep等函数）后再重新检查共享内存，避免过度占用CPU资源
        // 示例等待1秒后再次检查共享内存是否有新消息
        usleep(10000);
    }
    // printf("********共享内存读取线程结束****************\n");
    // 分离共享内存，如果分离失败，打印错误信息

    return NULL;
}

//子线程接收登录后服务器发送过来的消息并显示（在登录时创建）
void *fun1(int *sockfd)
{
	int msgid,run = 1;
	char end0[1024];
	char buf[1024];
	msgid = msgget((key_t)60,IPC_CREAT | 0666);//创建消息队列  参数：键值（电脑看），权限

	while(run)
	{
		int n = recv(*sockfd,buf,1024,0);
		if(!strncmp(buf,"end",3))  //输入end时退出聊天状态，并关闭显示线程（防止影响主函数中recv的接收）
		{
			sprintf(end0,"用户%s已退出聊天",name0);
			usleep(10000);
			ex=1;
			usleep(10000);
			strcpy(mybuf.buf,end0);
			mybuf.type=1;
			if((msgsnd(msgid,(void *)&mybuf,1024,0))<0)    //发送消息队列   队列id，将接收到的内容存到buf，0（接收第一个消息），0（阻塞）   没消息时阻塞
			{
				perror("msgnd");
			}
			break;
			run =0;
		}
		if (n == 0) {
			// 服务端已关闭套接字
			close(*sockfd);
			exit(0);
		}else{
			printf("%s\n",buf);
		}
		strcpy(mybuf.buf,buf);
	    mybuf.type=1;
		if((msgsnd(msgid,(void *)&mybuf,1024,0))<0)    //发送消息队列   队列id，将接收到的内容存到buf，0（接收第一个消息），0（阻塞）   没消息时阻塞
        {
            perror("msgnd");
        }
		
	}
    
}


void getCurrentTime(char *timeBuffer)
{
    time_t currentTime;
    struct tm *timeInfo;

    currentTime = time(NULL);
    timeInfo = localtime(&currentTime);

    // 格式化为 "YYYY-MM-DD HH:MM:SS" 的字符串
    strftime(timeBuffer, 30, "%Y-%m-%d %H:%M:%S", timeInfo);
}

void Regist(int sockfd)
{
	char name[20];
	char pwd[20];
	char buf[20];
	while(1)
	{
		printf("请输入您要注册的用户名\n");
		scanf("%s",name);
		send(sockfd,name,strlen(name)+1,0);//发送用户名

		recv(sockfd,buf,20,0);//接收用户名是否存在的信息
		if(strcmp(buf,"0")==0)
		{
			printf("用户名已存在,请重新输入\n");
			continue;
		}

		while(1)
		{
			printf("请输入密码\n");
			scanf("%s",pwd);
			send(sockfd,pwd,strlen(pwd)+1,0);//发送密码
			system("clear");
			printf("注册成功\n");
			return ;
		}
	}

}

void login(int sockfd)
{
	char name[20];
	char pwd[20];
	char buf[20];
	while(1)
	{
		printf("用户名：");
		scanf("%s",name);
		send(sockfd,name,strlen(name)+1,0);//发送用户名

		recv(sockfd,buf,20,0);//接收用户名是否存在的信息
		if(strcmp(buf,"-1")==0)
		{
			printf("用户不存在,请重新输入\n");
			continue;
		}

		while(1)
		{
			printf("密  码：");
			scanf("%s",pwd);
			send(sockfd,pwd,strlen(pwd)+1,0);//发送密码

			recv(sockfd,buf,20,0);//接收密码是否正确的信息
			if(strcmp(buf,"-1")==0)//比较，相等返回0，小于-1，大于1
			{
				printf("密码输入有误,请重新输入\n");
				continue;
			}
            system("clear");
			printf("登录成功\n");
			strcpy(name0,name);//拷贝
			printf("======================   登 录 成 功 ， 即 将 进 入   ======================\n");
			sleep(1);
			return ;
		}
	}
}

void writeSharedMemory()
{
    // printf("********进入共享内存写入循环****************\n");
    while (1)
    {
		readThreadBlocked = 0;
        // 获取用户输入写入共享内存
        scanf("%s", shmData->messages[0].content);
		char currentTime[30];
		getCurrentTime(currentTime);  // 获取当前时间
		strcpy(shmData->messages[0].name, name0);  // 填充用户名
		strcpy(shmData->messages[0].time, currentTime);  // 填充时间
        if (!strcmp(shmData->messages[0].content, "end"))
        {
			int readThreadBlocked = 1;
            break;
        }
        // 这里简单处理消息数量，假设只记录最新的10条消息，可根据实际调整逻辑
        if (shmData->messageCount < 10)
        {
            shmData->messageCount++;
        }
        else
        {
            // 消息数量已满，将后面的消息往前移一位，覆盖最旧的消息
			int i = 0;
            for (i < 9; i++;)
            {
                strcpy(shmData->messages[i].content, shmData->messages[i + 1].content);
            }
        }
    }
    // printf("********共享内存写入结束****************\n");
    // 分离共享内存，如果分离失败，打印错误信息
}


//  ./c 127.0.0.1
int main(int argc,char *argv[])
{
	int sockfd;
	struct sockaddr_in c;
	char buf[1024];
	pthread_t id;
	readThreadEnd = 0;
	int dd;//用于退出客户端

	shmid = shmget((key_t)1234, sizeof(struct SharedMemoryData), IPC_CREAT | 0666);  // 使用一个合适的键值（这里示例用1234，实际可按规则选取），设置权限等创建共享内存段
    if (shmid == -1)
    {
        perror("shmget创建共享内存失败");
        return 1;  // 根据实际情况决定错误返回值，这里简单示例返回1表示出错
    }
    // 将共享内存段连接到进程的地址空间
    void *temp_shmat = shmat(shmid, NULL, 0);
    if (temp_shmat == (void *)-1)
    {
        perror("shmat连接共享内存到进程空间失败");
        return 1;
    }
    shmData = (struct SharedMemoryData *)temp_shmat;
    shmData->messageCount = 0;  // 初始化消息数量为0



	
    system("clear");
	printf("\n");
	printf("                  ◢▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄◣  \n");
	printf("                  ※        ******                ******      ※\n");
	printf("                  ※      ******◆   赛 博 蜂 巢   ◆ ******    ※\n");
	printf("                  ※        ******                ******      ※\n");
	printf("                  ※==========================================※\n");
	printf("                  ※    ******        ******        ******    ※\n");
	printf("                  ※   *      *      *      *      *      *   ※\n");
	printf("                  ※***        ******        ******        ***※\n");
	printf("                  ※   *      *      *      *      *      *   ※\n");
	printf("                  ※    ******        ******        ******    ※\n");
	printf("                  ※   *      *      *      *      *      *   ※\n");
	printf("                  ※***        ******        ******        ***※\n");
	printf("                  ※   *      *      *      *      *      *   ※\n");
	printf("                  ※    ******        ******        ******    ※\n");
	printf("                  ※==========================================※\n");
	printf("                  ※                                          ※\n");
	printf("                  ※ ◆◆◆◆     欢迎来到 赛博蜂巢 聊天室   ◆◆◆◆ ※\n");
	printf("                  ※                                          ※\n");
	printf("请输入选项        ※▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄※  \n");
	

	//1 创建socket
	sockfd = socket(AF_INET,SOCK_STREAM,0);

	//准备结构体
	c.sin_family = AF_INET;
	c.sin_port = PORT;
	c.sin_addr.s_addr = inet_addr(argv[1]);//函数作用是将数组转为ip，接收到的服务端地址（命令中输入）
	bzero(&(c.sin_zero),8);

	//2 链接
	connect(sockfd,(struct sockaddr*)&c,sizeof(struct sockaddr));//作用连接accept，参数：

	//让用户选择要登录、注册
	
	while(1)
	{
		printf("① 注册\n");
		printf("② 登录\n");
		printf("③ 退出\n");
		// printf("请输入选项\n");

		scanf("%s",buf);//输入要发送的数据
		send(sockfd,buf,strlen(buf)+1,0);//发送。套接字描述符，发送的数据，计算发送数据的长度（/0加1，标志一般为0）
		
		switch(buf[0])
		{
		case '1':
			{
				//注册
				Regist(sockfd);
				break;
			}
		case '2':
			{
				//登录
				login(sockfd);


				char n[20];
				int d;
				char c;
			    while(1)
				{
					system("clear");
					printf("                  ※▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄  \n");
					printf("                  ※                                          \n");
					printf("                  ※  1 开始聊天\n");
					printf("                  ※  2 修改密码\n");
					printf("                  ※  3 查看所有好友\n");
					printf("                  ※  4 查询在线好友\n");
					printf("                  ※  5 注销账号\n");
					printf("                  ※  6 退出登录\n");
					printf("                  ※                                          \n");
					printf("                  ※==========================================\n");
					printf("                  ※请输入选项\n");
					printf("                  ※▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄  \n");
					if(d==1)
					{
            		printf("输入有误，请重新输入\n");
					d=0;
					}
					scanf("%s",n);
					send(sockfd,n,strlen(n)+1,0);//发送。套接字描述符，发送的数据，计算发送数据的长度（/0加1，标志一般为0）
					if(recv(sockfd,buf,1024,0)<=0)
					{
						close(sockfd);
						exit(0);
					}
					switch (n[0])
					{
						case '1': 
						{
									int ret = pthread_create(&id, 0, (void *)fun1, (void *)&sockfd);
									if (ret != 0) 
									{
										perror("fail");// 线程创建失败，处理错误
									}
									pthread_t readThread;
									int readRet = pthread_create(&readThread, 0, (void *)readSharedMemory, NULL);
									if (readRet!= 0)
									{
										perror("创建共享内存读取线程失败");
									}

									while (1) 
									{
										system("clear");
										printf("                  ※▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄  \n");
										printf("                  ※                                          \n");
										printf("                  ※  当前用户：%s                  \n", name0);
										printf("                  ※==========================================\n");
										printf("                  ※    发送'@'开启私聊\n");
										printf("                  ※    发送'$'开始群聊\n");
										printf("                  ※==========================================\n");          
										printf("                  ※ ◆ 发送'end',退出相应聊天         \n");
										printf("                  ※ ◆ 输入end返回菜单              \n");
										printf("                  ※                                          \n");
										printf("                  ※▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄  \n");
										
										char buf1[20];
										//主线程用于发送数据
										while(1)
										{
											scanf("%s",buf);
											while ((c = getchar())!= '\n' && c!= EOF);//清空输入缓冲区，避免scanf函数跳过的问题。
											//sprintf(buf,"%s:%s",name0,buf);
											send(sockfd,buf,strlen(buf)+1,0);
											usleep(10000);
											if(!strncmp(buf,"end",3))  //输入end时退出聊天状态，并关闭显示线程（防止影响主函数中recv的接收）
											{
												
												// 等待展示消息线程结束
								
												break;
											}
											
											if (!strncmp(buf, "$", 1))  // 检测到输入 $，启动共享内存读取线程并进入共享内存写入函数
											{
												readThreadEnd = 0;
												// 创建共享内存读取线程
												pthread_t readThread;
												int readRet = pthread_create(&readThread, 0, (void *)readSharedMemory, NULL);

												if (readRet!= 0)
												{
													perror("创建共享内存读取线程失败");
												}
												// 进入共享内存写入函数
												writeSharedMemory();
												// 写入结束后，通知读取线程结束循环
												readThreadEnd = 1;
												// 等待读取线程结束
												// 重置读取线程结束标志
												
												break;
											}
										}
										
										usleep(50000);
										
										if(ex==1)  //接收到服务器发来的end后退出聊天选择状态返回菜单
										{
											ex=0;
											break;
										}
									}
							// 取消线程
							// pthread_cancel(id);
							// 等待线程退出
							pthread_join(id, NULL);
							pthread_join(readThread, NULL);
							break;
						}
						case '2'://修改密码
						{
							char buf1[1024];
							char x[20];
							char buf2[1024];
							char pwd[20];
							while(1)
							{
								
								recv(sockfd,buf1,1024,0);
								printf("%s",buf1);
								scanf("%s",x);
								send(sockfd,x,strlen(x)+1,0);
								if(x[0]=='y' || x[0]=='Y')
								{
									while(1)
									{
										recv(sockfd,buf2,1024,0);
										printf("%s",buf2);
										scanf("%s",pwd);
										send(sockfd,pwd,strlen(pwd)+1,0);
										printf("修改成功\n");
										sleep(1);
										break;
									}
								}
								
								break;
							}
							break;
						}
						case '3'://查询所有好友
						{
							
							char buf[1024];
							printf("*=====================================*\n");
							printf("用户名\t密码\tsockfd\t在线状态\n");
							while(1)
							{
								int n = recv(sockfd,buf,1024,0);
								if (n == 0) {
									// 服务端已关闭套接字
									close(sockfd);
									exit(0);
								}
								if (!strncmp(buf,"end",3))
								{
									printf("*=====================================*\n");
									printf("请按下回车键退出\n");
									while ((c = getchar()) != '\n' && c != EOF);
									getchar();
									break;
								}
								printf("%s\n",buf);
													
							}
							
							break;
						}
							
						case '4':
						{
							
							char buf[1024];
							printf("*=====================================*\n");
							printf("用户名\t密码\tsockfd\t在线状态\n");
							while(1)
							{
								int n = recv(sockfd,buf,1024,0);
								if (n == 0) {
									// 服务端已关闭套接字
									close(sockfd);
									exit(0);
								}
								if (!strncmp(buf,"end",3))
								{
									printf("*=====================================*\n");
									printf("请按下回车键退出\n");
									while ((c = getchar()) != '\n' && c != EOF);
									getchar();
									break;
								}
								printf("%s\n",buf);					
							}
							
							break;
							
						}
						case '5':
						{
							          d=2;
									break;
							
						}
						case '6':
						{
							          d=3;
									break;
							
						}
						default:
							{
									printf("输入有误，请重新输入\n");
									d=1;
									break;
							}
						
					}
					if(d==3)
					{
						d=0;
						system("clear");
						break;
					}
					if(d==2)
					{
						d=0;
						system("clear");
						printf("注销成功\n");
						break;
					}
					
					
				}
				break;
			}
		case '3':
		{
			dd=1;
			break;
		}
		default:
			{
				printf("输入有误，请重新输入\n");
				break;
			}
		}
		if(dd==1)
		{
			dd=0;		
			break;
		}
	}

	readThreadEnd = 1;

    if (shmdt((void *)shmData) == -1)
    {
        perror("shmdt");
    }
	// 撤销共享内存
	if (shmctl(shmid, IPC_RMID, 0) < 0)
	{
		perror("shmctl撤销共享内存失败");
	}
	
    printf("正在退出客户端...\n");
	sleep(2);
	printf("已成功退出\n");
	//4 关闭
	close(sockfd);
}
